// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"internal/bytealg"
	"runtime"
	"sync"
)

// BUG（rsc，mikio）：在DragonFly BSD和OpenBSD上，在
// “tcp”和“udp”网络上侦听时，不会同时侦听IPv4和IPv6 
// 连接。这是因为IPv4通信不会被路由到IPv6套接字——如果要支持两个地址系列，则需要两个单独的套接字。
// 有关详细信息，请参阅inet6（4）。

type ipStackCapabilities struct {
	sync.Once             // 
	ipv4Enabled           bool
	ipv6Enabled           bool
	ipv4MappedIPv6Enabled bool
}

var ipStackCaps ipStackCapabilities

// supportsIPv4报告平台是否支持IPv4网络
// 功能。
func supportsIPv4() bool {
	ipStackCaps.Once.Do(ipStackCaps.probe)
	return ipStackCaps.ipv4Enabled
}

// 支持SIPV6报告平台是否支持IPv6网络
// 功能。huang jian defg
func supportsIPv6() bool {
	ipStackCaps.Once.Do(ipStackCaps.probe)
	return ipStackCaps.ipv6Enabled
}

func supportsIPv4map() bool {
	// 一些操作系统不支持将IPv4地址映射到IPv6，不需要运行时检查。
	switch runtime.GOOS {
	case "dragonfly", "openbsd":
		return false
	}

	ipStackCaps.Once.Do(ipStackCaps.probe)
	return ipStackCaps.ipv4MappedIPv6Enabled
}

// addrList表示网络端点地址的列表。
type addrList []Addr

// isIPv4报告addr是否包含IPv4地址。
func isIPv4(addr Addr) bool {
	switch addr := addr.(type) {
	case *TCPAddr:
		return addr.IP.To4() != nil
	case *UDPAddr:
		return addr.IP.To4() != nil
	case *IPAddr:
		return addr.IP.To4() != nil
	}
	return false
}

// isNotIPv4报告addr是否不包含IPv4地址。
func isNotIPv4(addr Addr) bool { return !isIPv4(addr) }

// forResolve返回address for 
// 对ResolveTCPAddr、ResolveUDPAddr或ResolveIPAddr的调用中最合适的地址。
// 首选IPv4，除非addr包含IPv6文本。
func (addrs addrList) forResolve(network, addr string) Addr {
	var want6 bool
	switch network {
	case "ip":
		// IPv6文本（地址不包含端口）
		want6 = count(addr, ':') > 0
	case "tcp", "udp":
		// IPv6文本。（addr包含一个端口，所以请查找“[”）
		want6 = count(addr, '[') > 0
	}
	if want6 {
		return addrs.first(isNotIPv4)
	}
	return addrs.first(isIPv4)
}

// 首先返回满足策略的第一个地址，或者如果
// 没有，则返回任何类型的第一个地址。
func (addrs addrList) first(strategy func(Addr) bool) Addr {
	for _, addr := range addrs {
		if strategy(addr) {
			return addr
		}
	}
	return addrs[0]
}

// 分区将地址列表分为两类，使用
// 策略函数为每个地址分配一个布尔标签。
// 第一个地址和任何具有匹配标签的地址都作为
// 原数返回，而具有相反标签的地址作为回退返回
// 。对于非空输入，原色保证为
// 非空。
func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks addrList) {
	var primaryLabel bool
	for i, addr := range addrs {
		label := strategy(addr)
		if i == 0 || label == primaryLabel {
			primaryLabel = label
			primaries = append(primaries, addr)
		} else {
			fallbacks = append(fallbacks, addr)
		}
	}
	return
}

// 过滤器地址列表将过滤器应用于IP地址列表，
// 生成地址对象列表。已知的过滤器为nil、仅限IPV4、
// 和仅限IPV6。当过滤器为nil时，它返回每个地址。
// 错误为零时，结果至少包含一个地址。
func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr, originalAddr string) (addrList, error) {
	var addrs addrList
	for _, ip := range ips {
		if filter == nil || filter(ip) {
			addrs = append(addrs, inetaddr(ip))
		}
	}
	if len(addrs) == 0 {
		return nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: originalAddr}
	}
	return addrs, nil
}

// IPv4仅报告addr是否为IPv4地址。
func ipv4only(addr IPAddr) bool {
	return addr.IP.To4() != nil
}

// IPv6仅报告addr是否为除IPv4映射的IPv6地址之外的IPv6地址。
func ipv6only(addr IPAddr) bool {
	return len(addr.IP) == IPv6len && addr.IP.To4() == nil
}

// SplitHostPort将格式为“主机：端口”、“主机%zone:端口”、“主机]：端口”或“[主机%zone]：端口”的网络地址拆分为主机或
// host%区域和端口。
// 
// 主机端口中的文字IPv6地址必须包含在方括号中，如“[：：1]：80”，“[：：1%lo0]：80”中所示。
// 
// 有关主机端口参数、主机
// 和端口结果的说明，请参阅func Dial。
func SplitHostPort(hostport string) (host, port string, err error) {
	const (
		missingPort   = "missing port in address"
		tooManyColons = "too many colons in address"
	)
	addrErr := func(addr, why string) (host, port string, err error) {
		return "", "", &AddrError{Err: why, Addr: addr}
	}
	j, k := 0, 0

	// 端口在最后一个冒号之后开始。
	i := last(hostport, ':')
	if i < 0 {
		return addrErr(hostport, missingPort)
	}

	if hostport[0] == '[' {
		// 在最后一个“：”之前期待第一个“]”。
		end := bytealg.IndexByteString(hostport, ']')
		if end < 0 {
			return addrErr(hostport, "missing ']' in address")
		}
		switch end + 1 {
		case len(hostport):
			// 现在在“]”后面不可能有“：”了。
			return addrErr(hostport, missingPort)
		case i:
			// 预期结果。
		default:
			// 要么']'后面不跟冒号，要么是
			// 后面跟的冒号不是最后一个。
			if hostport[end+1] == ':' {
				return addrErr(hostport, tooManyColons)
			}
			return addrErr(hostport, missingPort)
		}
		host = hostport[1:end]
		j, k = 1, end+1 // 在这些位置之前不能有“['resp.]”
	} else {
		host = hostport[:i]
		if bytealg.IndexByteString(host, ':') >= 0 {
			return addrErr(hostport, tooManyColons)
		}
	}
	if bytealg.IndexByteString(hostport[j:], '[') >= 0 {
		return addrErr(hostport, "unexpected '[' in address")
	}
	if bytealg.IndexByteString(hostport[k:], ']') >= 0 {
		return addrErr(hostport, "unexpected ']' in address")
	}

	port = hostport[i+1:]
	return host, port, nil
}

func splitHostZone(s string) (host, zone string) {
	// IPv6作用域的寻址区域标识符在
	// 最后百分号之后开始。
	if i := last(s, '%'); i > 0 {
		host, zone = s[:i], s[i+1:]
	} else {
		host = s
	}
	return
}

// JoinHostPort将主机和端口合并为
// 的网络地址，格式为“主机：端口”。如果主机包含冒号，如文本
// IPv6地址中所示，则JoinHostPort返回“[host]：port”。
// 
// 有关主机和端口参数的说明，请参阅func Dial。
func JoinHostPort(host, port string) string {
	// 如果主机有
	// 冒号，我们假设主机是一个文本IPv6地址。
	if bytealg.IndexByteString(host, ':') >= 0 {
		return "[" + host + "]:" + port
	}
	return host + ":" + port
}

// InternetAddressList解析地址，地址可以是文字IP 
// 地址或DNS名称，并返回internet协议
// 系列地址的列表。
// 错误为零时，结果至少包含一个地址。
func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addrList, error) {
	var (
		err        error
		host, port string
		portnum    int
	)
	switch net {
	case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
		if addr != "" {
			if host, port, err = SplitHostPort(addr); err != nil {
				return nil, err
			}
			if portnum, err = r.LookupPort(ctx, net, port); err != nil {
				return nil, err
			}
		}
	case "ip", "ip4", "ip6":
		if addr != "" {
			host = addr
		}
	default:
		return nil, UnknownNetworkError(net)
	}
	inetaddr := func(ip IPAddr) Addr {
		switch net {
		case "tcp", "tcp4", "tcp6":
			return &TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
		case "udp", "udp4", "udp6":
			return &UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
		case "ip", "ip4", "ip6":
			return &IPAddr{IP: ip.IP, Zone: ip.Zone}
		default:
			panic("unexpected network: " + net)
		}
	}
	if host == "" {
		return addrList{inetaddr(IPAddr{})}, nil
	}

	// 尝试使用文字IP地址，然后使用DNS名称。
	ips, err := r.lookupIPAddr(ctx, net, host)
	if err != nil {
		return nil, err
	}
	// 问题18806：如果机器已将
	// IPv6配置为可以绑定到：“（IPv6未指定）
	// 但不要重新连接到同一地址，请返回
	// 重新拨打0.0.0.0。
	if len(ips) == 1 && ips[0].IP.Equal(IPv6unspecified) {
		ips = append(ips, IPAddr{IP: IPv4zero})
	}

	var filter func(IPAddr) bool
	if net != "" && net[len(net)-1] == '4' {
		filter = ipv4only
	}
	if net != "" && net[len(net)-1] == '6' {
		filter = ipv6only
	}
	return filterAddrList(filter, ips, inetaddr, host)
}

func loopbackIP(net string) IP {
	if net != "" && net[len(net)-1] == '6' {
		return IPv6loopback
	}
	return IP{127, 0, 0, 1}
}
